package com.alibaba.algo;

import com.alibaba.algo.DBTools.TableColumn;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;

import java.io.*;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * The Class GenerateKernel.
 */
public class GenerateKernel {

    /**
     * Generate to file.
     *
     * @param dataMap    the data map
     * @param tableName  the table name
     * @param moduleName the module name
     */
    public static void generateToFile(Map<String, Object> dataMap, String tableName, String moduleName) {
        if (StringUtils.isNotBlank(tableName)) {
            dataMap = readFromDB(dataMap, tableName, moduleName);
            //        if ("dao".equals(moduleName) || "entity".equals(moduleName) || "mapper".equals(moduleName)) {
            //        }
            // 生成文件
            generateFile(moduleName + ".ftl", moduleName, dataMap);
            System.out.println(String.format("*************************生成[%s%s]成功",
                    StringUtils.isEmpty(tableName) ? "" : tableName + ".", moduleName));
        }

    }

    /**
     * Read from DB.
     *
     * @param data       the data
     * @param tableName  the table name
     * @param moduleName the module name
     * @return the map
     */
    private static Map<String, Object> readFromDB(Map<String, Object> data, String tableName, String moduleName) {

        data.put(VariablesConstant.ENTITY_NAME,
                Tools.formatFieldCapital(DBTools.transferTableName(tableName)));
        if (StringUtils.isNotEmpty(GCConfig.TABLE_PREFIX)) {

            String noPrefix = tableName.split(GCConfig.TABLE_PREFIX)[1];
            if (noPrefix.startsWith("_")) {
                noPrefix = noPrefix.substring(1);
            }
            data.put(VariablesConstant.ENTITY_NAME_NO_PREFIX,
                    Tools.formatFieldCapital(DBTools.transferTableName(noPrefix)));
        } else {
            data.put(VariablesConstant.ENTITY_NAME_NO_PREFIX,
                    Tools.formatFieldCapital(DBTools.transferTableName(tableName)));
        }
        data.put(VariablesConstant.TABLE_NAME, DBTools.transferTableName(tableName));

        data.put(VariablesConstant.TABLE_ID, GCConfig.TABLE_ID);
        data.put(VariablesConstant.TABLE_DB_ID, GCConfig.TABLE_DB_ID);
        data.put(VariablesConstant.SELECT_BY_PK_WHERE_CLAUSE, "id= #{" + GCConfig.TABLE_ID + "}");
        Map<String, String> dbnameMap;
        try {
            dbnameMap = DBTools.getDbNameDescByComma(tableName);
            data.put("dbnameNoTableId", dbnameMap.get("dbnameNoTableId"));
            data.put("dbnameWithTableId", dbnameMap.get("dbnameWithTableId"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        List<TableColumn> entityColumns = DBTools.readTableColumn(tableName);
        data.put(VariablesConstant.TABLE_DESCRIPTION, entityColumns.get(0).getTableDescrition());
        data.put(VariablesConstant.ENTITY_COLUMNS, entityColumns);
        data.put(VariablesConstant.FILTER_ENTITY_COLUMNS, getFilterEntityColumns(entityColumns));
        data.put(VariablesConstant.PRIMARY_KEY_TYPE, "Long");

        return data;
    }

    private static List<TableColumn> getFilterEntityColumns(List<TableColumn> entityColumns) {
        String exculdeColumn = GCConfig.EXCLUDE_DOMAIN_COLUMN;
        if (StringUtils.isEmpty(exculdeColumn)) {
            return entityColumns;
        }
        List<TableColumn> domainColumns = new ArrayList<>();
        for (TableColumn tableColumn : entityColumns) {
            boolean exist = false;
            for (String exc : exculdeColumn.split(",")) {
                if (tableColumn.getFieldName().equals(exc)) {
                    exist = true;
                    break;
                }

            }

            if (!exist) {
                TableColumn domainColumn = new TableColumn();
                BeanUtils.copyProperties(tableColumn, domainColumn);
                domainColumns.add(domainColumn);
            }

        }
        return domainColumns;
    }

    /**
     * Generate file.
     *
     * @param templateFileName the template file name
     * @param type             the type
     * @param data             the data
     */
    // finally产生文件
    private static void generateFile(String templateFileName, String type, Map<?, ?> data) {
        Writer out = null;
        try {

            Object entityNameObj = data.get(VariablesConstant.ENTITY_NAME);
            String entityName = entityNameObj == null ? null : String.valueOf(entityNameObj);
            String fileNamePath = "";
            if ("mapper".equals(type)) {
                fileNamePath = GCConfig.MAPPER_PATH + entityName + "Mapper.xml";
            } else {
                fileNamePath = getCodePath(type, entityName,
                        (String) data.get(VariablesConstant.ENTITY_NAME_NO_PREFIX));
            }
            String fileDir = StringUtils.substringBeforeLast(fileNamePath, "/");
            Template template = getConfiguration().getTemplate(templateFileName);
            FileUtils.forceMkdir(new File((new StringBuilder(String.valueOf(fileDir))).append("/").toString()));
            out = new OutputStreamWriter(new FileOutputStream(fileNamePath), "UTF-8");
            template.process(data, out);
            out.close();
        } catch (Exception e) {
            System.out.println("exception" + e.getMessage());
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                System.out.println("exception" + e.getMessage());
            }
        }
    }

    /**
     * Gets the code path.
     *
     * @param type       the type
     * @param entityName the entity name
     * @return the code path
     */
    // 获取代码路径
    private static String getCodePath(String type, String entityName, String entityNameNoPrefix) {
        String path = GCConfig.CODE_PATH;
        StringBuilder str = new StringBuilder();
        if (StringUtils.isNotBlank(type)) {
            ModuleType module = ModuleType.valueOf(type);
            str.append(path);
            // 修正包名顺序不正确bug
            str.append(StringUtils.lowerCase(module.getRelativePath()));

            if ("dao".equals(type) || "entity".equals(type) || "mapper".equals(type)) {
                str.append("/").append(entityName);
            } else if ("service".equals(type) || "serviceImpl".equals(type)) {
                str.append("/").append(entityNameNoPrefix);
            } else {
                String code = GCConfig.getProductCode();
                if (StringUtils.isEmpty(code)) {
                    str.append("/");
                } else {
                    str.append("/").append(code.substring(0, 1).toUpperCase() + code.substring(1));
                }
            }
            str.append(module.getSuffix());
            if ("domain".equals(type)) {
                str.append(entityName).append("Domain");
            } else if ("dto".equals(type)) {
                str.append(entityNameNoPrefix).append("DTO");
            } else if ("convert".equals(type)) {
                str.append(entityName).append("Convert");
            }else if ("controller".equals(type)) {
                str.append(entityNameNoPrefix).append("Controller");
            }
            str.append(".java");
        } else {
            throw new IllegalArgumentException("type is null");
        }
        return str.toString();
    }

    /**
     * Gets the configuration.
     *
     * @return the configuration
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private static Configuration getConfiguration() throws IOException {
        Configuration cfg = new Configuration();
        String path = GCConfig.getTemplatePath();
        File templateDirFile = new File(path);
        cfg.setDirectoryForTemplateLoading(templateDirFile);
        cfg.setLocale(Locale.CHINA);
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }

    /**
     * The Enum ModuleType.
     */
    public static enum ModuleType {

        /**
         * The mapper.
         */
        mapper("", "", "", false),

        /**
         * The dao.
         */
        dao("Mapper", "dao", VariablesConstant.DAO_PACKAGE, false),
        entity("", "entity", VariablesConstant.ENTITY_PACKAGE, false),
        service("Service", "service", VariablesConstant.SERVICE_PACKAGE, false),
        serviceImpl("ServiceImpl", "service/impl", "", false),
        dto("", "dto", VariablesConstant.DTO_PACKAGE, false),
        convert("", "convert", VariablesConstant.CONVERT_PACKAGE, false),
        controller("", "controller", VariablesConstant.CONTROLLER_PACKAGE, false),
        domain("", "domain", VariablesConstant.DOMAIN_PACKAGE, false),
        ;

        /**
         * The suffix.
         */
        private String suffix;

        /**
         * The relative path.
         */
        private String relativePath;

        /**
         * The variable.
         */
        private String variable;

        /**
         * The path need to add product code.
         */
        private boolean pathNeedToAddProductCode;

        /**
         * Instantiates a new module type.
         *
         * @param suffix                   the suffix
         * @param relativePath             the relative path
         * @param variable                 the variable
         * @param pathNeedToAddProductCode the path need to add product code
         */
        private ModuleType(String suffix, String relativePath, String variable, boolean pathNeedToAddProductCode) {
            this.suffix = suffix;
            this.relativePath = relativePath;
            this.variable = variable;
            this.pathNeedToAddProductCode = pathNeedToAddProductCode;
        }

        /**
         * Gets the suffix.
         *
         * @return the suffix
         */
        public String getSuffix() {
            return suffix;
        }

        /**
         * Gets the relative path.
         *
         * @return the relative path
         */
        public String getRelativePath() {
            return relativePath;
        }

        /**
         * Gets the isneed to add product code.
         *
         * @return the isneed to add product code
         */
        public boolean getIsneedToAddProductCode() {
            return pathNeedToAddProductCode;
        }

        /**
         * Gets the variable.
         *
         * @return the variable
         */
        public String getVariable() {
            return variable;
        }

    }

    /**
     * The Class UpdateContent.
     */
    public static class UpdateContent implements Serializable {

        /**
         * The Constant serialVersionUID.
         */
        private static final long serialVersionUID = 1L;

        /**
         * The module.
         */
        private String module;

        /**
         * The file.
         */
        private String file;

        /**
         * The detail.
         */
        private List<UpdateContentDetail> detail;

        /**
         * Gets the module.
         *
         * @return the module
         */
        public String getModule() {
            return module;
        }

        /**
         * Sets the module.
         *
         * @param module the new module
         */
        public void setModule(String module) {
            this.module = module;
        }

        /**
         * Gets the file.
         *
         * @return the file
         */
        public String getFile() {
            return file;
        }

        /**
         * Sets the file.
         *
         * @param file the new file
         */
        public void setFile(String file) {
            this.file = file;
        }

        /**
         * Gets the detail.
         *
         * @return the detail
         */
        public List<UpdateContentDetail> getDetail() {
            return detail;
        }

        /**
         * Sets the detail.
         *
         * @param detail the new detail
         */
        public void setDetail(List<UpdateContentDetail> detail) {
            this.detail = detail;
        }

    }

    /**
     * The Class UpdateContentDetail.
     */
    public static class UpdateContentDetail implements Serializable {

        /**
         * The Constant serialVersionUID.
         */
        private static final long serialVersionUID = 1L;

        /**
         * The row.
         */
        private int row;

        /**
         * The content.
         */
        private String content;

        /**
         * Gets the row.
         *
         * @return the row
         */
        public int getRow() {
            return row;
        }

        /**
         * Sets the row.
         *
         * @param row the new row
         */
        public void setRow(int row) {
            this.row = row;
        }

        /**
         * Gets the content.
         *
         * @return the content
         */
        public String getContent() {
            return content;
        }

        /**
         * Sets the content.
         *
         * @param content the new content
         */
        public void setContent(String content) {
            this.content = content;
        }

    }
}
